#include "CheckersApp.h"
#include "CheckersBoard.h"
#include "CheckersPiece.h"
#include "SlideAnimation.h"
#include "JumpAnimation.h"
#include "Camera.h"
#include "PSColor.h" // Generated header file with shader byte code
#include "VSColor.h" // Generated header file with shader byte code

CheckersApp::CheckersApp():
	blackPiece(NULL),
	camera(NULL),
	cbPerObject(NULL),
	checkersBoard(NULL),
	ibBlock(NULL),
	ibCylinder(NULL),
	inputLayout(NULL),
	psColor(NULL),
	redPiece(NULL),
	vbBlock(NULL),
	vbCylinder(NULL),
	vsColor(NULL)
{
}

CheckersApp::~CheckersApp()
{
	delete blackPiece;
	delete camera;
	delete checkersBoard;
	delete redPiece;
	ReleaseCOM(cbPerObject);
	ReleaseCOM(ibBlock);
	ReleaseCOM(ibCylinder);
	ReleaseCOM(inputLayout);
	ReleaseCOM(psColor);
	ReleaseCOM(vbBlock);
	ReleaseCOM(vbCylinder);
	ReleaseCOM(vsColor);

	// Clean up remaining animations
	std::vector<Animation*>::iterator animationItr = animations.begin();
	while (animationItr != animations.end())
		delete *animationItr;
}

HRESULT CheckersApp::InitializeBlockVertices()
{
	//------------------------------------------------------------------------------
	// Create vertex and index buffers for rendering a checkerboard block
	//------------------------------------------------------------------------------

	Vertex vertices[8] =
	{
		DirectX::XMFLOAT3( 0.5f,  0.1f,  0.5f),
		DirectX::XMFLOAT3(-0.5f,  0.1f,  0.5f),
		DirectX::XMFLOAT3(-0.5f,  0.1f, -0.5f),
		DirectX::XMFLOAT3( 0.5f,  0.1f, -0.5f),
		DirectX::XMFLOAT3( 0.5f, -0.1f,  0.5f),
		DirectX::XMFLOAT3(-0.5f, -0.1f,  0.5f),
		DirectX::XMFLOAT3(-0.5f, -0.1f, -0.5f),
		DirectX::XMFLOAT3( 0.5f, -0.1f, -0.5f)
	};

	D3D11_BUFFER_DESC desc;
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	desc.ByteWidth = sizeof(vertices);
	desc.CPUAccessFlags = 0;
	desc.MiscFlags = 0;
	desc.StructureByteStride = 0;
	desc.Usage = D3D11_USAGE_IMMUTABLE;

	D3D11_SUBRESOURCE_DATA initialData;
	initialData.pSysMem = vertices;
	initialData.SysMemPitch = 0;
	initialData.SysMemSlicePitch = 0;

	HRESULT hr = D3DDevice()->CreateBuffer(&desc, &initialData, &vbBlock);
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateBuffer");

	USHORT indices[36] =
	{
		0, 3, 1, 2, 1, 3,		// Top face
		4, 5, 6, 7, 4, 6,		// Bottom face
		0, 1, 4, 5, 4, 1,		// Back face
		2, 3, 6, 3, 7, 6,		// Front face
		3, 0, 4, 7, 3, 4,		// Right face
		1, 2, 5, 6, 5, 2		// Left face
	};
	
	desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	desc.ByteWidth = sizeof(indices);
	desc.CPUAccessFlags = 0;
	desc.MiscFlags = 0;
	desc.StructureByteStride = 0;
	desc.Usage = D3D11_USAGE_IMMUTABLE;
	
	initialData.pSysMem = indices;
	initialData.SysMemPitch = 0;
	initialData.SysMemSlicePitch = 0;

	hr = D3DDevice()->CreateBuffer(&desc, &initialData, &ibBlock);
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateBuffer");

	return S_OK;
}

HRESULT CheckersApp::InitializeCylinderVertices()
{
	//------------------------------------------------------------------------------
	// Create vertex and index buffers for rendering a checker piece
	//------------------------------------------------------------------------------

	CheckersApp::Vertex *vertices = new CheckersApp::Vertex[CheckersPiece::NUM_CIRCLE_VERTICES * 2 + 2];
	for (int i = 0; i < CheckersPiece::NUM_CIRCLE_VERTICES; ++i)
	{
		float theta = 2.0f * DirectX::XM_PI / CheckersPiece::NUM_CIRCLE_VERTICES * i;
		vertices[i].pos = DirectX::XMFLOAT3(CheckersPiece::CHECKERS_PIECE_RADIUS * DirectX::XMScalarCos(theta), 0.1f, CheckersPiece::CHECKERS_PIECE_RADIUS * DirectX::XMScalarSin(theta));
		vertices[CheckersPiece::NUM_CIRCLE_VERTICES + i].pos = DirectX::XMFLOAT3(CheckersPiece::CHECKERS_PIECE_RADIUS * DirectX::XMScalarCos(theta), -0.1f, CheckersPiece::CHECKERS_PIECE_RADIUS * DirectX::XMScalarSin(theta));
	}
	vertices[2 * CheckersPiece::NUM_CIRCLE_VERTICES].pos =  DirectX::XMFLOAT3(0.0f, 0.1f, 0.0f);		// Top center
	vertices[2 * CheckersPiece::NUM_CIRCLE_VERTICES + 1].pos =  DirectX::XMFLOAT3(0.0f, -0.1f, 0.0f);	// Bottom center

	D3D11_BUFFER_DESC desc;
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	desc.ByteWidth = (CheckersPiece::NUM_CIRCLE_VERTICES * 2 + 2) * sizeof(CheckersApp::Vertex);
	desc.CPUAccessFlags = 0;
	desc.MiscFlags = 0;
	desc.StructureByteStride = 0;
	desc.Usage = D3D11_USAGE_IMMUTABLE;

	D3D11_SUBRESOURCE_DATA initialData;
	initialData.pSysMem = vertices;
	initialData.SysMemPitch = 0;
	initialData.SysMemSlicePitch = 0;

	HRESULT hr = D3DDevice()->CreateBuffer(&desc, &initialData, &vbCylinder);
	delete [] vertices;
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateBuffer");

	USHORT *indices = new USHORT[CheckersPiece::NUM_INDICES];
	for (int i = 0; i < CheckersPiece::NUM_CIRCLE_VERTICES; ++i)
	{
		// Top triangle
		indices[3*i] = 2 * CheckersPiece::NUM_CIRCLE_VERTICES;
		indices[3*i + 1] = (i + 1) % CheckersPiece::NUM_CIRCLE_VERTICES;
		indices[3*i + 2] = i % CheckersPiece::NUM_CIRCLE_VERTICES;

		// Bottom triangle
		indices[CheckersPiece::NUM_CIRCLE_VERTICES * 3 + 3*i] = 2 * CheckersPiece::NUM_CIRCLE_VERTICES + 1;
		indices[CheckersPiece::NUM_CIRCLE_VERTICES * 3 + 3*i + 1] = i % CheckersPiece::NUM_CIRCLE_VERTICES + CheckersPiece::NUM_CIRCLE_VERTICES;
		indices[CheckersPiece::NUM_CIRCLE_VERTICES * 3 + 3*i + 2] = (i + 1) % CheckersPiece::NUM_CIRCLE_VERTICES + CheckersPiece::NUM_CIRCLE_VERTICES;

		// Side triangles
		indices[CheckersPiece::NUM_CIRCLE_VERTICES * 6 + 6*i] = i % CheckersPiece::NUM_CIRCLE_VERTICES;
		indices[CheckersPiece::NUM_CIRCLE_VERTICES * 6 + 6*i + 1] = (i + 1) % CheckersPiece::NUM_CIRCLE_VERTICES + CheckersPiece::NUM_CIRCLE_VERTICES;
		indices[CheckersPiece::NUM_CIRCLE_VERTICES * 6 + 6*i + 2] = i % CheckersPiece::NUM_CIRCLE_VERTICES + CheckersPiece::NUM_CIRCLE_VERTICES;
		indices[CheckersPiece::NUM_CIRCLE_VERTICES * 6 + 6*i + 3] = i % CheckersPiece::NUM_CIRCLE_VERTICES;
		indices[CheckersPiece::NUM_CIRCLE_VERTICES * 6 + 6*i + 4] = (i + 1) % CheckersPiece::NUM_CIRCLE_VERTICES;
		indices[CheckersPiece::NUM_CIRCLE_VERTICES * 6 + 6*i + 5] = (i + 1) % CheckersPiece::NUM_CIRCLE_VERTICES + CheckersPiece::NUM_CIRCLE_VERTICES;
	}
	
	desc.BindFlags = D3D11_BIND_INDEX_BUFFER; 
	desc.ByteWidth = CheckersPiece::NUM_INDICES * sizeof(USHORT);
	desc.CPUAccessFlags = 0;
	desc.MiscFlags = 0;
	desc.StructureByteStride = 0;
	desc.Usage = D3D11_USAGE_IMMUTABLE;
	
	initialData.pSysMem = indices;
	initialData.SysMemPitch = 0;
	initialData.SysMemSlicePitch = 0;

	hr = D3DDevice()->CreateBuffer(&desc, &initialData, &ibCylinder);
	delete [] indices;
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateBuffer");

	return S_OK;
}

void CheckersApp::OnDrawFrame()
{
	// Calculate view-proj matrix
	viewProjMatrix = DirectX::XMMatrixMultiply(camera->ViewMatrix(), projMatrix);

	// Clear back buffer and depth/stencil buffer
	DirectX::XMFLOAT4 clearColor(0.39f, 0.58f, 0.93f, 1.0f);
	D3DDeviceContext()->ClearRenderTargetView(RenderTargetView(), reinterpret_cast<FLOAT *>(&clearColor));
	D3DDeviceContext()->ClearDepthStencilView(DepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

	// Draw game objects
	checkersBoard->Draw();
	blackPiece->Draw();
	redPiece->Draw();

	// Animation manager
	std::vector<Animation*>::iterator it = animations.begin();
	while (it != animations.end())
	{
		Animation *anim = *it;

		if (anim->Done())
		{
			delete anim;
			it = animations.erase(it);
		}
		else
		{
			anim->Tick();
			it++;
		}
	}
}

HRESULT CheckersApp::OnInitialize()
{
	//--------------------------------------------------------------------------
	// Create the camera
	//--------------------------------------------------------------------------
	camera = new Camera(WindowHandle());
	camera->SetPhi(DirectX::XM_PI / 3.0f);
	camera->SetRho(11.0f);
	camera->SetRhoLimits(7.0f, 15.0f);

	//--------------------------------------------------------------------------
	// Create the constant buffer that holds the WVP matrix
	//--------------------------------------------------------------------------
	
	// Buffer description
	D3D11_BUFFER_DESC constBufferDesc;
	ZeroMemory(&constBufferDesc, sizeof(constBufferDesc));
	constBufferDesc.ByteWidth = sizeof(ObjectParams);
	constBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	constBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	constBufferDesc.CPUAccessFlags = 0;

	// Create the buffer
	HRESULT hr = D3DDevice()->CreateBuffer(&constBufferDesc, NULL, &cbPerObject);
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateBuffer");

	//--------------------------------------------------------------------------
	// Create the shaders
	//--------------------------------------------------------------------------
	hr = D3DDevice()->CreateVertexShader(g_VSColor, sizeof(g_VSColor), NULL, &vsColor);
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateVertexShader");

	hr = D3DDevice()->CreatePixelShader(g_PSColor, sizeof(g_PSColor), NULL, &psColor);
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreatePixelShader");

	//--------------------------------------------------------------------------
	// Create the vertex layout
	//--------------------------------------------------------------------------
	D3D11_INPUT_ELEMENT_DESC inputElement[1] =
	{
		{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}
	};

	hr = D3DDevice()->CreateInputLayout(inputElement, 1, g_VSColor, sizeof(g_VSColor), &inputLayout);
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateInputLayout");
	
	//--------------------------------------------------------------------------
	// Initialize vertex/index buffers
	//--------------------------------------------------------------------------
	hr = InitializeBlockVertices();
	if (FAILED(hr))
		return hr;

	hr = InitializeCylinderVertices();
	if (FAILED(hr))
		return hr;
	
	//--------------------------------------------------------------------------
	// Game objects
	//--------------------------------------------------------------------------
	checkersBoard = new CheckersBoard(this);
	checkersBoard->SetHighlightColor(0, DirectX::XMFLOAT3(0.88f, 0.07f, 0.37f));
	checkersBoard->SetHighlightColor(1, DirectX::XMFLOAT3(0.45f, 0.76f, 0.40f));
	checkersBoard->SetWorldMatrix(DirectX::XMMatrixIdentity());

	blackPiece = new CheckersPiece(this);
	blackPiece->PlaceOnBoard(checkersBoard, 0, 0);
	blackPiece->SetColor(DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f));

	redPiece = new CheckersPiece(this);
	redPiece->PlaceOnBoard(checkersBoard, 3, 6);
	redPiece->SetColor(DirectX::XMFLOAT3(1.0f, 0.0f, 0.0f));

	return S_OK;
}

// See the "Picking" chapter in Frank Luna's book for info on how this works
HRESULT CheckersApp::OnLeftMouseDown(int x, int y)
{
	// Compute picking ray in view space
	DirectX::XMFLOAT4X4 pMatrix;
	DirectX::XMStoreFloat4x4(&pMatrix, projMatrix);
	float vx = (+2.0f*x/WindowWidth()  - 1.0f)/pMatrix._11;
	float vy = (-2.0f*y/WindowHeight() + 1.0f)/pMatrix._22;
	DirectX::XMFLOAT4 rayOrigin(0.0f, 0.0f, 0.0f, 1.0f);
	DirectX::XMFLOAT4 rayDir(vx, vy, 1.0f, 0.0f);

	// Compute picking ray in world space
	DirectX::XMMATRIX vInv = DirectX::XMMatrixInverse(NULL, camera->ViewMatrix());
	DirectX::XMVECTOR rayOriginV, rayDirV;
	rayOriginV = DirectX::XMLoadFloat4(&rayOrigin);
	rayOriginV = DirectX::XMVector4Transform(rayOriginV, vInv);
	DirectX::XMStoreFloat4(&rayOrigin, rayOriginV);
	rayDirV = DirectX::XMLoadFloat4(&rayDir);
	rayDirV = DirectX::XMVector4Transform(rayDirV, vInv);
	DirectX::XMStoreFloat4(&rayDir, rayDirV);

	// Find intersection point of picking ray with y = 0.1 plane
	float t = (0.1f - rayOrigin.y) / rayDir.y;
	float bx = rayOrigin.x + t * rayDir.x;
	float bz = rayOrigin.z + t * rayDir.z;

	// Clear highlights
	checkersBoard->ClearHighlights();

	// Highlight square if (x,z) point falls within checkerboard range
	if (bx >= -4.0f && bx < 4.0f && bz >= -4.0f && bz < 4.0f)
	{
		// Convert to checkerboard indices
		int i = static_cast<int>(bz + 4.0f), j = static_cast<int>(bx + 4.0f);

		// Highlight clicked square
		checkersBoard->Highlight(i, j, 1);

		// Move black piece to square
		JumpAnimation *anim = new JumpAnimation(this, blackPiece);
		anim->SetInterval(1.0f);
		anim->MoveToSquare(checkersBoard, i, j);
		StartAnimation(anim);
	}

	return S_OK;
}

HRESULT CheckersApp::OnMouseMove(int x, int y)
{
	camera->OnMouseMove(x, y);

	return S_OK;
}

HRESULT CheckersApp::OnMouseWheel(int x, int y, float delta)
{
	camera->OnMouseWheel(x, y, delta);

	return S_OK;
}

HRESULT CheckersApp::OnResize()
{
	// Create a new projection matrix based on aspect ratio
	projMatrix = DirectX::XMMatrixPerspectiveFovLH(DirectX::XM_PIDIV4, (float)WindowWidth() / WindowHeight(), 1.0f, 100.0f);

	return S_OK;
}

HRESULT CheckersApp::OnRightMouseDown(int x, int y)
{
	camera->OnRightMouseDown(x, y);

	return S_OK;
}

HRESULT CheckersApp::OnRightMouseUp(int x, int y)
{
	camera->OnRightMouseUp(x, y);

	return S_OK;
}